package org.farng.mp3.filename;

import org.farng.mp3.AbstractMP3Tag;
import org.farng.mp3.TagOptionSingleton;
import org.farng.mp3.id3.AbstractFrameBodyTextInformation;
import org.farng.mp3.id3.AbstractFrameBodyUrlLink;
import org.farng.mp3.id3.AbstractID3v2Frame;
import org.farng.mp3.id3.AbstractID3v2FrameBody;
import org.farng.mp3.id3.FrameBodyCOMM;
import org.farng.mp3.id3.ID3v2_4;
import org.farng.mp3.id3.ID3v2_4Frame;

import java.util.Iterator;


/**
 * This composite subclass is the leaf of the tree. It contains the actual
 * strings found in the filename.
 *
 * @author Eric Farng
 * @version $Revision: 1.11 $
 */
public class FilenameToken extends AbstractFilenameComposite {
    /** what information this object represents. */
    private Class id3v2FrameBodyClass = null;

    /** token that this object represents */
    private String token;

    /**
     * Creates a new FilenameToken object.
     */
    public FilenameToken() {}

    public FilenameToken(FilenameToken copyObject) {
        super(copyObject);

        try {
            this.id3v2FrameBodyClass = id3v2FrameBodyClass.newInstance().getClass();
        } catch (IllegalAccessException ex) {
            throw new IllegalStateException("IllegalAccessException: No access to run constructor to create copy");
        } catch (InstantiationException ex) {
            throw new IllegalStateException("InstantiationException: Unable to instantiate constructor to copy");
        }

        this.token = new String(copyObject.token);
    }

    /**
     * DOCUMENT ME!
     *
     * @param frame DOCUMENT ME!
     */
    public void setFrame(AbstractID3v2Frame frame) {
        if ((id3v2FrameBodyClass != null) && id3v2FrameBodyClass.equals(frame.getBody().getClass())) {
            /**
             * @todo add support for more tag matches. only doing text
             *       information and URL links right now because i'm lazy
             */
            if (AbstractFrameBodyTextInformation.class.isInstance(frame.getBody())) {
//            if (id3v2FrameBodyClass.equals(AbstractFrameBodyTextInformation.class)) {
                token = ((AbstractFrameBodyTextInformation) frame.getBody()).getText();
            } else if (AbstractFrameBodyUrlLink.class.isInstance(frame.getBody())) {
//            } else if (id3v2FrameBodyClass.equals(AbstractFrameBodyUrlLink.class)) {
                token = ((AbstractFrameBodyUrlLink) frame.getBody()).getUrlLink();
            }
        }
    }

    /**
     * Sets the ID3v2 frame body that this token represents
     *
     * @param id3v2FrameBodyClass the ID3v2 frame body that this token
     *        represents
     */
    public void setId3v2FrameBodyClass(Class id3v2FrameBodyClass) {
        this.id3v2FrameBodyClass = id3v2FrameBodyClass;
    }

    /**
     * Returns the ID3v2 frame body that this token represents
     *
     * @return the ID3v2 frame body that this token represents
     */
    public Class getId3v2FrameBodyClass() {
        return id3v2FrameBodyClass;
    }

    /**
     * Sets the token that this class contains
     *
     * @param token the token that this class contains
     */
    public void setToken(String token) {
        this.token = token.trim();
    }

    /**
     * Return the token that this class contains
     *
     * @return the token that this class contains
     */
    public String getToken() {
        return token;
    }

    /**
     * Reconstruct the filename that is represented by this composite.
     *
     * @return the filename that is represented by this composite.
     */
    public String composeFilename() {
        return token;
    }

    /**
     * DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public ID3v2_4 createId3Tag() {
        ID3v2_4 tag = new ID3v2_4();

        if (id3v2FrameBodyClass != null) {
            try {
                AbstractID3v2FrameBody body = (AbstractID3v2FrameBody) id3v2FrameBodyClass.newInstance();

                /**
                 * @todo need to add support for more frame bodies here
                 */
                if (body instanceof AbstractFrameBodyTextInformation) {
                    ((AbstractFrameBodyTextInformation) body).setText(token);
                    ((AbstractFrameBodyTextInformation) body).setTextEncoding((byte) 0);
                } else if (body instanceof AbstractFrameBodyUrlLink) {
                    ((AbstractFrameBodyUrlLink) body).setUrlLink(token);
                } else if (body instanceof FrameBodyCOMM) {
                    ((FrameBodyCOMM) body).setText(token);
                    ((FrameBodyCOMM) body).setDescription("");
                    ((FrameBodyCOMM) body).setLanguage(TagOptionSingleton.getInstance().getLanguage());
                    ((FrameBodyCOMM) body).setTextEncoding(TagOptionSingleton.getInstance().getTextEncoding());
                }

                ID3v2_4Frame frame = new ID3v2_4Frame();
                frame.setBody(body);
                tag.setFrame(frame);
            } catch (IllegalAccessException ex) {
                // catch and dont' create the frame
            } catch (InstantiationException ex) {
                // catch and dont' create the frame
            }
        }

        return tag;
    }

    /**
     * Returns an iterator through each <code>FilenameToken</code> in this
     * composite in the correct order for the file name.
     *
     * @return an iterator through each <code>FilenameToken</code> in this
     *         composite
     */
    public Iterator iterator() {
        return new FilenameTokenIterator(this);
    }

    /**
     * Match all elements of this composite against the keywords for this class
     * type found in <code>TagOptionSingleton</code>. If the
     * <code>FilenameToken</code> matches the keyword, the token's class is
     * set.
     *
     * @param id3v2FrameBodyClass Class of keywords to match against.
     */
    public void matchAgainstKeyword(Class id3v2FrameBodyClass) {
        if (AbstractID3v2FrameBody.class.isAssignableFrom(id3v2FrameBodyClass)) {
            if (TagOptionSingleton.getInstance().isCompositeMatchOverwrite() || (this.id3v2FrameBodyClass == null)) {
                Iterator iterator = TagOptionSingleton.getInstance().getKeywordListIterator(id3v2FrameBodyClass);
                String   lowerCaseToken = token.toLowerCase();

                while (iterator.hasNext()) {
                    String matchString = ((String) iterator.next()).toLowerCase();

                    if (matchString.equals(lowerCaseToken) || (matchString.indexOf(lowerCaseToken) >= 0) || (lowerCaseToken.indexOf(matchString) >= 0)) {
                        setId3v2FrameBodyClass(id3v2FrameBodyClass);

                        break;
                    }
                }
            }
        }
    }

    /**
     * Match all elements of this composite against the given tag. If any
     * element of <code>matchTag</code> matches any element of this tag's
     * composite, then this tag's composite leaf node's class is set.
     *
     * @param matchTag Tag to match against
     */
    public void matchAgainstTag(AbstractMP3Tag matchTag) {
        if (TagOptionSingleton.getInstance().isCompositeMatchOverwrite() || (id3v2FrameBodyClass == null)) {
            ID3v2_4 tag;

            if (matchTag instanceof ID3v2_4) {
                tag = (ID3v2_4) matchTag;
            } else {
                tag = new ID3v2_4(matchTag);
            }

            Iterator               iterator = tag.iterator();
            AbstractID3v2Frame     frame;
            AbstractID3v2FrameBody body;
            String                 matchString = null;
            String                 lowerCaseToken = token.toLowerCase();

            while (iterator.hasNext()) {
                frame = (ID3v2_4Frame) iterator.next();
                body  = (AbstractID3v2FrameBody) frame.getBody();

                /**
                 * @todo add support for more tag matches. only doing text
                 *       information and URL links right now because i'm lazy
                 */
                if (body instanceof AbstractFrameBodyTextInformation) {
                    matchString = ((AbstractFrameBodyTextInformation) body).getText().toLowerCase();
                } else if (body instanceof AbstractFrameBodyUrlLink) {
                    matchString = ((AbstractFrameBodyUrlLink) body).getUrlLink().toLowerCase();
                } else if (body instanceof FrameBodyCOMM) {
                    matchString = ((FrameBodyCOMM) body).getText();
                }

                if (matchString!=null && (matchString.equals(lowerCaseToken) || (matchString.indexOf(lowerCaseToken) >= 0) || (lowerCaseToken.indexOf(matchString) >= 0))) {
                    setId3v2FrameBodyClass(body.getClass());

                    break;
                }
            }
        }
    }

    /**
     * Returns a string containing debug information about this class
     *
     * @return a string containing debug information about this class
     */
    public String toString() {
        return id3v2FrameBodyClass + ": " + token;
    }
}